package com.example.doipandroidclient;

import android.annotation.SuppressLint;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.example.doipandroidclient.blueTooth.blueTooth.BlueToothDoipClient;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;

import java.io.InputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;

import core.crypto.GlobalCertifications;
import core.doipMessage.DoipMessage;
import core.doipMessage.DoipResponseCode;
import core.exception.IrpClientException;
import core.model.handleRecord.DoHandleRecord;
import core.model.handleRecord.DoipServiceHandleRecord;
import core.model.metadata.SearchParameter;
import core.utils.DoipGson;
import core.utils.GlobalConfigurations;
import endpoint.doipClient.DoipClient;
import endpoint.doipClient.DoipClientChannelGenerator;
import endpoint.doipClient.DoipClientImpl;
import endpoint.doipClient.DoipMessageCallback;
import endpoint.doipServer.DoipListenerInfo;
import endpoint.irpClient.GlobalIrpClient;
import endpoint.irpClient.IrpClient;

public class MainActivity extends AppCompatActivity {

    final String TAG = "DOIP";
    MainActivity instance;

    //input views
    private TextView arguments;
    private TextView targetID;
    private String doipOperation;
    IrpClient irpClient;

    //response views
    public static final int UPDATE_RESPONSE = 1;
    public static final int UPDATE_LOG = 2;
    private String responseCode;
    private TextView responseCodeView;
    private String response;
    private TextView responseView;
    private String requestTime;
    private TextView requestTimeView;

    //log view
    private String requestLog = " >";
    private TextView requestLogView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        instance = this;
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        responseCodeView = findViewById(R.id.responseCodeContent);
        responseView = findViewById(R.id.responseContent);
        requestTimeView = findViewById(R.id.requestTimeContent);
        requestLogView = findViewById(R.id.logContent);

        try {
            init();
            irpClient = GlobalIrpClient.getGlobalClient();
        } catch (Exception e) {
            e.printStackTrace();
        }

        arguments =findViewById(R.id.args);
        targetID = findViewById(R.id.targetID);
        String[] methods=new String[]{"选择方法","hello","listOperations","retrieve","delete","create","update","search"};

        ArrayAdapter<String> adapter;
        adapter=new ArrayAdapter<>(this,android.R.layout.simple_spinner_item,methods);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        Spinner mspinner = findViewById(R.id.operation);
        mspinner.setAdapter(adapter);

        mspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                doipOperation =adapter.getItem(position);
                assert doipOperation != null;
                switch (doipOperation) {
                    case "选择方法":
                    case "listOperations":
                    case "hello":
                        arguments.setText("");
                        targetID.setText("86.5000.470/doip.CloudTCPService");
                        break;
                    case "retrieve":
                        arguments.setText("{\"includeElement\":\"true\"}");
                        targetID.setText("86.5000.470/do.s49g31ZvmR");
                        break;
                    case "delete":
                        arguments.setText("{\"repoID\":\"86.5000.470/doip.CloudTCPService\"}");
                        break;
                    case "create":
                        arguments.setText("{\"id\":\"nullable\",\"type\":\"0.Type/DO\",\"attributes\":{\"key\":\"value\"},\"elements\":[{\"id\":\"a\",\"type\":\"string\",\"attributes\":{\"key\":\"value\"},\"data\":\"helloWorld\"}]}");
                        break;
                    case "update":
                        arguments.setText("{\"id\":\"notnull\",\"type\":\"0.Type/DO\",\"attributes\":{\"key\":\"value\"},\"elements\":[{\"id\":\"a\",\"type\":\"string\",\"attributes\":{\"key\":\"value\"},\"data\":\"helloWorld\"}]}");
                        break;
                    case "search":
                        targetID.setText("86.5000.470/doip.CloudTlsRegistry");
                        arguments.setText("{\"query\":\"repositories\"}");
                        break;
                }
            }
            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        Button callDOIP = findViewById(R.id.send);
        callDOIP.setOnClickListener(v -> {
            try {
                onCallButtonClick();
            }catch (Exception e){
                e.printStackTrace();
            }
        });

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(v -> {
            requestLog = "";
            Message msg = new Message();
            msg.what = UPDATE_LOG;
            responseHandler.sendMessage(msg);
        });
    }

    public void onCallButtonClick(){
        String targetID = ((TextView)findViewById(R.id.targetID)).getText().toString();
        debug(targetID);
        String args = ((TextView)findViewById(R.id.args)).getText().toString();
        debug(args);
        JsonObject attr = DoipGson.getDoipGson().fromJson(args,JsonObject.class);

        switch (doipOperation){
            case "hello":
                debug("call hello");
                new Thread(() -> {
                    DoipClient doipClient;
                    try {
                        doipClient = getDoipClient(targetID);
                        info("sending DOIP request");
                        doipClient.hello(targetID,new onResponse(System.currentTimeMillis()));
                    } catch (Exception e) {
                        info(e.getMessage());
                        e.printStackTrace();
                    }
                }).start();
                break;
            case "listOperations":
                debug("call listOperations");
                new Thread(() -> {
                    DoipClient doipClient;
                    try {
                        doipClient = getDoipClient(getTargetRepoByDoID(targetID));
                        doipClient.listOperations(targetID,new onResponse(System.currentTimeMillis()));
                    } catch (Exception e) {
                        info(e.getMessage());
                        e.printStackTrace();
                    }
                }).start();
                break;
            case "retrieve":
                debug("call retrieve");
                new Thread(() -> {
                    DoipClient doipClient;
                    try {
                        doipClient = getDoipClient(getTargetRepoByDoID(targetID));
                        String doID = targetID;
                        String eleID = attr.get("elementID") == null?null:attr.get("elementID").getAsString();
                        boolean includeED = attr.get("includeElementData") != null && attr.get("includeElementData").getAsString().equals("true");
                        info(targetID + ":" + eleID + ":" + includeED);
                        doipClient.retrieve(doID,
                                eleID,
                                includeED,
                                new onResponse(System.currentTimeMillis()));
                    } catch (Exception e) {
                        info(e.getMessage());
                        e.printStackTrace();
                    }
                }).start();
                break;
            case "delete":
                debug("call delete");
                new Thread(() -> {
                    DoipClient doipClient;
                    try {
                        doipClient = getDoipClient(attr.get("repoID").getAsString());
                        doipClient.delete(targetID,new onResponse(System.currentTimeMillis()));
                    } catch (Exception e) {
                        info(e.getMessage());
                        e.printStackTrace();
                    }
                }).start();
                break;
            case "create":
                debug("call create");
                info("not support yet!!");
                break;
            case "update":
                debug("call update");
                info("not support yet!!");
                break;
            case "search":
                debug("call search");
                new Thread(() -> {
                    DoipClient doipClient;
                    try {
                        doipClient = getDoipClient(targetID);
                        SearchParameter sp = SearchParameter.getDefaultParameter();
                        sp.query = attr.get("query").getAsString();
                        doipClient.search(sp,new onResponse(System.currentTimeMillis()));
                    } catch (Exception e) {
                        info(e.getMessage());
                        e.printStackTrace();
                    }
                }).start();
                break;
            default:
                debug("call nothing");
       }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public void init() throws Exception {
        AndroidInitializer.init();
        AssetManager am = this.getAssets();
        //或者AssetManager am = this.getAssets();
        GlobalConfigurations.certPath = "keys/dou.TEST.keystore.bks";
        InputStream is = am.open(GlobalConfigurations.certPath);

        try {
            GlobalCertifications.localKeyStore.load(is, GlobalConfigurations.certPassword.toCharArray());
        }catch (Exception e){
            Log.d(TAG,"load keystore error");
            e.printStackTrace();
        }

        Log.d(TAG,"keys: " + new Gson().toJson(GlobalCertifications.localKeyStore.aliases()));
        PublicKey pk = GlobalCertifications.localKeyStore.getCertificate("mykey").getPublicKey();
        PrivateKey sk = (PrivateKey)GlobalCertifications.localKeyStore.getKey("mykey", GlobalConfigurations.certPassword.toCharArray());
        GlobalCertifications.localKeypair = new KeyPair(pk, sk);
        GlobalIrpClient.globalIrpClient = new AndroidCdiIrpClient(GlobalCertifications.localKeypair
                , GlobalConfigurations.User_Handle
                , GlobalConfigurations.LHS_Address);
        DoipClientChannelGenerator.addClient("bluetooth",new BlueToothDoipClient(this));
    }


    private void debug(String s){
        Log.d(TAG,s);
    }

    private void info(String s){
        Log.i(TAG,s);
        requestLog = requestLog + s + "\n >" ;
        Message msg = new Message();
        msg.what = UPDATE_LOG;
        responseHandler.sendMessage(msg);
    }

    String getTargetRepoByDoID(String doID) throws IrpClientException {
        info("Try to resolve doID");
        DoHandleRecord doHR = new DoHandleRecord(irpClient.resolve(doID));
        info("Digital Object handle record: " + new Gson().toJson(doHR));
        return doHR.getRepository();
    }

    DoipClient getDoipClient(String repoID) throws IrpClientException {
        info("Try to resolve address of target DOIP service");
        DoipServiceHandleRecord repoHR = new DoipServiceHandleRecord(irpClient.resolve(repoID));
        List<DoipListenerInfo> listenerInfos = new Gson().fromJson(repoHR.getListeners(), new TypeToken<List<DoipListenerInfo>>() {
        }.getType());
        info("DOIP service listener information: " + repoHR.getListeners());

        DoipClient doipClient = new DoipClientImpl();
        for (DoipListenerInfo listener: listenerInfos) {
            doipClient.connect(listener.url);
            if(doipClient.isConnected()){
                info("Choose first available address to connect, url: " + listener.url);
                return doipClient;
            }
        }
        info("No available schema");
        return doipClient;
    }

    class onResponse implements DoipMessageCallback{
        long startTime;

        onResponse(long time){
            startTime = time;
        }
        @Override
        public void onResult(DoipMessage doipMessage) {
            try{
                info("got DOIP response");
                long rt = System.currentTimeMillis() - startTime;
                requestTime = rt + "ms";

                debug("message header: " + DoipGson.getDoipGson().toJson(doipMessage.header.parameters));
                responseCode = doipMessage.header.parameters.response.getName();

                if(doipMessage.header.parameters.response == DoipResponseCode.Success)
                    response = doipMessage.body.getDataAsJsonString();
                else
                    response = new String(doipMessage.body.getEncodedData());
                debug("message Body: " + response);

                Message msg = new Message();
                msg.what = UPDATE_RESPONSE;
                responseHandler.sendMessage(msg);

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @SuppressLint("HandlerLeak")
    private Handler responseHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case UPDATE_RESPONSE:
                    responseCodeView.setText(responseCode);
                    debug("response: " + response);
                    responseView.setText(response);
                    requestTimeView.setText(requestTime);
                    break;
                case UPDATE_LOG:
                    requestLogView.setText(requestLog);
                    break;
                default:
                    debug("invalid message flag");
                    break;
            }
        }
    };
}
